package net.sudot.commons.lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 使用Redis服务实现的分布式锁
 *
 * @author tangjialin on 2018-03-18.
 */
@Aspect
@Component
public class RedisLockAspect {
    private final Map<String, Expression> keyCache = new ConcurrentHashMap<>(128);
    protected Logger logger = LoggerFactory.getLogger(getClass());
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获得执行锁
     * 执行后释放锁
     *
     * @param pjp 接入点包装对象实例
     */
    @Around("@annotation(net.sudot.commons.lock.Lock)")
    protected Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Lock annotation = targetMethod.getAnnotation(Lock.class);
        String key = annotation.value();
        if (key != null && !key.isEmpty()) {
            String cacheKey = annotation.name() + ":" + key;
            Expression expression = keyCache.get(cacheKey);
            if (expression == null) {
                expression = spelExpressionParser.parseExpression(key);
                keyCache.put(cacheKey, expression);
            }
            StandardEvaluationContext context = new StandardEvaluationContext();
            Object[] args = pjp.getArgs();
            Parameter[] parameters = targetMethod.getParameters();
            for (int index = args.length - 1; index >= 0; index--) {
                Object arg = args[index];
                context.setVariable(parameters[index].getName(), arg);
                context.setVariable("p" + index, arg);
            }
            Object expressionValue = expression.getValue(context);
            if (expressionValue == null) {
                StringBuilder builder = new StringBuilder(256);
                builder.append("Lock注解中value表达式计算结果不能为空.方法[").append(targetMethod.toString()).append("]参数列表:");
                for (Parameter parameter : parameters) {
                    builder.append(parameter.getName()).append(",");
                }
                builder.deleteCharAt(builder.length() - 1);
                throw new IllegalArgumentException(builder.toString());
            }
            key = String.valueOf(expressionValue);
        }
        java.util.concurrent.locks.Lock lock = new RedisLock(redisTemplate, annotation.name(), key, Duration.ofSeconds(annotation.expirationTime()));
        if (!lock.tryLock(annotation.timeout(), TimeUnit.SECONDS)) {
            if (!annotation.throwUnlock()) { return null; }
            throw new UnlockException(((RedisLock) lock).getKey());
        }
        try {
            return pjp.proceed();
        } finally {
            lock.unlock();
        }
    }

}